/*
 * MainFrame.java
 *
 * Created on __DATE__, __TIME__
 */

package com.assjirc.frames;

import java.awt.event.KeyEvent;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.Position;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;

import org.scannotation.AnnotationDB;
import org.scannotation.ClasspathUrlFinder;

import com.assjirc.annotations.Command;
import com.assjirc.commands.Executable;
import com.assjirc.enums.ReturnStatus;
import com.assjirc.interfaces.GuiEventHandler;
import com.assjirc.irc.Channel;
import com.assjirc.irc.IrcManager;
import com.assjirc.irc.Message;
import com.assjirc.pircbothack.User;
import com.assjirc.utils.SequentialQueue;
import com.assjirc.utils.UniqueStack;

/**
 * 
 * @author __USER__
 */
public class GUI extends javax.swing.JFrame implements GuiEventHandler {

	/***/
	private static final long serialVersionUID = -8611383511335255722L;

	private static IrcManager ircmanager;

	private static SimpleDateFormat sdf = new SimpleDateFormat("kk:mm:ss");

	private static String TITLE_PREPEND = "assjirc";

	private UniqueStack<Channel> channelStack = new UniqueStack<Channel>();

	private SequentialQueue<String> inputQueue = new SequentialQueue<String>();

	private Map<String, Object> commandMap = new HashMap<String, Object>();

	private static final String BOOT_MESSAGE = "for help, type /help";

	public Map<String, Object> getCommandMap() {
		return commandMap;
	}

	/** Creates new form MainFrame */
	public GUI() {
		findAnnotated(Command.class.getName(), "com.assjirc");
		initComponents();
		ircmanager = new IrcManager(this);
		inputTextField.requestFocus();
		channelTextArea.append(BOOT_MESSAGE);
	}

	/**
	 * This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The
	 * content of this method is always regenerated by the Form Editor.
	 */
	// GEN-BEGIN:initComponents
	// <editor-fold defaultstate="collapsed" desc="Generated Code">
	private void initComponents() {

		channelTreeScrollPane = new javax.swing.JScrollPane();
		channelTree = new javax.swing.JTree(new DefaultTreeModel(new DefaultMutableTreeNode("root")));
		channelTree.addTreeSelectionListener(new ChannelTreeListener());
		channelTextAreaScrollPane = new javax.swing.JScrollPane();
		channelTextArea = new javax.swing.JTextArea();
		userListScrollPane = new javax.swing.JScrollPane();
		userList = new javax.swing.JList();
		inputTextField = new javax.swing.JTextField();
		errorLabel = new javax.swing.JLabel();

		setTitle("jirc");
		setDefaultCloseOperation(3);
		setName("guiFrame");

		channelTree.setRootVisible(false);
		channelTreeScrollPane.setViewportView(channelTree);

		channelTextArea.setColumns(20);
		channelTextArea.setEditable(false);
		channelTextArea.setLineWrap(true);
		channelTextArea.setRows(1);
		channelTextArea.setWrapStyleWord(true);
		channelTextArea.setTabSize(4);
		channelTextAreaScrollPane.setViewportView(channelTextArea);

		userList.setAutoscrolls(false);
		userListScrollPane.setViewportView(userList);

		inputTextField.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				inputTextFieldActionPerformed(evt);
			}
		});
		inputTextField.addFocusListener(new java.awt.event.FocusAdapter() {
			public void focusGained(java.awt.event.FocusEvent evt) {
				inputTextFieldFocusGained(evt);
			}
		});
		inputTextField.addKeyListener(new java.awt.event.KeyAdapter() {
			public void keyReleased(java.awt.event.KeyEvent evt) {
				inputTextFieldKeyReleased(evt);
			}
		});

		errorLabel.setHorizontalAlignment(11);
		errorLabel.setForeground(new java.awt.Color(255, 0, 0));

		javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
		getContentPane().setLayout(layout);
		layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(
			javax.swing.GroupLayout.Alignment.TRAILING,
			layout.createSequentialGroup().addGroup(
				layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false).addGroup(
					layout.createSequentialGroup().addComponent(channelTreeScrollPane,
						javax.swing.GroupLayout.PREFERRED_SIZE, 139, javax.swing.GroupLayout.PREFERRED_SIZE).addGap(2,
						2, 2)).addGroup(
					layout.createSequentialGroup().addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE,
						javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE).addPreferredGap(
						javax.swing.LayoutStyle.ComponentPlacement.RELATED))).addGroup(
				layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(inputTextField,
					javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 475,
					Short.MAX_VALUE).addComponent(channelTextAreaScrollPane,
					javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 475,
					Short.MAX_VALUE)).addGap(2, 2, 2).addComponent(userListScrollPane,
				javax.swing.GroupLayout.PREFERRED_SIZE, 142, javax.swing.GroupLayout.PREFERRED_SIZE)));
		layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(
			javax.swing.GroupLayout.Alignment.TRAILING,
			layout.createSequentialGroup().addGroup(
				layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(userListScrollPane,
					javax.swing.GroupLayout.DEFAULT_SIZE, 477, Short.MAX_VALUE).addComponent(channelTextAreaScrollPane,
					javax.swing.GroupLayout.DEFAULT_SIZE, 477, Short.MAX_VALUE).addComponent(channelTreeScrollPane,
					javax.swing.GroupLayout.DEFAULT_SIZE, 477, Short.MAX_VALUE)).addGap(2, 2, 2).addGroup(
				layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false).addComponent(errorLabel,
					javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
					.addComponent(inputTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 22, Short.MAX_VALUE))));

		pack();
	}// </editor-fold>

	// GEN-END:initComponents

	private void inputTextFieldFocusGained(java.awt.event.FocusEvent evt) {
		if (inputTextField.getText().equals(BOOT_MESSAGE)) {
			inputTextField.setText("");
		}
	}

	private void inputTextFieldKeyReleased(java.awt.event.KeyEvent evt) {
		if (evt.getKeyCode() == KeyEvent.VK_UP) {
			inputTextField.setText(inputQueue.moveBack());
		} else if (evt.getKeyCode() == KeyEvent.VK_DOWN) {
			inputTextField.setText(inputQueue.moveForward());
		}
	}

	public void joinServer(String server, String nick, String password) {
		ReturnStatus serverStatus = ircmanager.joinServer(server, 6666, nick, password);
		if (serverStatus.equals(ReturnStatus.SUCCESS)) {
			DefaultMutableTreeNode serverNode = new DefaultMutableTreeNode(server);
			DefaultTreeModel model = (DefaultTreeModel) this.channelTree.getModel();
			model.insertNodeInto(serverNode, (MutableTreeNode) model.getRoot(), 0);
			this.channelTree.scrollPathToVisible(new TreePath(serverNode.getPath()));
			this.channelTree.setSelectionPath(new TreePath(serverNode.getPath()));
		} else if (serverStatus == ReturnStatus.NICK_ALREADY_IN_USE) {
			errorLabel.setText("nick already in use");
		} else {
			errorLabel.setText("error joining server");
		}
	}

	public void joinChannel(String channel, String password) {
		if (!ircmanager.isConnected()) {
			errorLabel.setText("not connected");
		} else {
			ReturnStatus status = ircmanager.joinChannelAndReturn(channel, password);
			if (status == ReturnStatus.SUCCESS) {
				DefaultMutableTreeNode channelNode = new DefaultMutableTreeNode(ircmanager.findChannelByName(channel));
				DefaultTreeModel model = (DefaultTreeModel) this.channelTree.getModel();
				MutableTreeNode root = (MutableTreeNode) model.getRoot();
				model.insertNodeInto(channelNode, (MutableTreeNode) root.getChildAt(0), 0);
				this.channelTree.setSelectionPath(new TreePath(channelNode.getPath()));
				this.setTitle("jirc: " + channel);
			} else if (status == ReturnStatus.ALREADY_IN_CHANNEL) {
				errorLabel.setText("already in channel");
			} else if (status == ReturnStatus.INVALID) {
				errorLabel.setText("invalid channel name");
			} else {
				errorLabel.setText("error joining channel");
			}
		}
	}

	public void partChannel(String channel, String reason) {
		if (!ircmanager.isConnected()) {
			errorLabel.setText("not connected");
		} else {
			ReturnStatus status = ircmanager.partChannelAndReturn(channel, reason);
			if (status == ReturnStatus.SUCCESS) {
				// update ui to reflect parting the channel
				TreePath path = channelTree.getNextMatch(channel, 0, Position.Bias.Forward);
				MutableTreeNode node = (MutableTreeNode) path.getLastPathComponent();
				DefaultTreeModel model = (DefaultTreeModel) channelTree.getModel();
				model.removeNodeFromParent(node);
				channelStack.pop();

				// update ui to go to the last channel visited
				Channel newChannel = channelStack.peek();
				path = channelTree.getNextMatch(newChannel.getName(), 0, Position.Bias.Forward);
				channelTree.setSelectionPath(path);
				System.out.println("parting: " + channel + " | with stack: " + channelStack);
			} else {
				errorLabel.setText("error parting room");
			}
		}
	}

	private void inputTextFieldActionPerformed(java.awt.event.ActionEvent evt) {
		errorLabel.setText("");
		String text = this.inputTextField.getText();
		if (text.startsWith(IrcManager.FUNCTION_CHAR)) {
			String name = null;
			String params = null;
			if (text.contains(" ")) {
				int index = text.indexOf(" ");
				name = text.substring(1, index).toLowerCase();
				params = text.substring(index + 1);
			} else {
				name = text.substring(1).toLowerCase();
			}

			Object command = commandMap.get(name);
			if (command != null) {
				((Executable) command).execute(params, this);
			} else {
				errorLabel.setText("invalid command");
			}
		} else {
			channelTextArea.append(prepareMessage(ircmanager.getNick(), text));
			IrcManager.currentChannel.appendText(prepareMessage(ircmanager.getNick(), text));
			String[] messages = text.split("\n");
			for (String message : messages) {
				ircmanager.sendMessage(IrcManager.currentChannel.getName(), message);
			}
		}
		inputQueue.push(text);
		this.inputTextField.setText("");
		channelTextArea.setCaretPosition(channelTextArea.getText().length());
	}

	/**
	 * @param args the command line arguments
	 */
	public static void main(String args[]) {
		java.awt.EventQueue.invokeLater(new Runnable() {
			public void run() {
				new GUI().setVisible(true);
			}
		});
	}

	// GEN-BEGIN:variables
	// Variables declaration - do not modify
	private javax.swing.JTextArea channelTextArea;

	private javax.swing.JScrollPane channelTextAreaScrollPane;

	private javax.swing.JTree channelTree;

	private javax.swing.JScrollPane channelTreeScrollPane;

	private javax.swing.JLabel errorLabel;

	private javax.swing.JTextField inputTextField;

	private javax.swing.JList userList;

	private javax.swing.JScrollPane userListScrollPane;

	// End of variables declaration//GEN-END:variables

	private void rebuildUserList(String channel) {
		User[] users = ircmanager.getUsers(channel);
		List<String> list = new ArrayList<String>();

		for (User user : users) {
			list.add(user.getPrefix() + user.getNick());
		}

		Collections.sort(list);

		userList.setListData(list.toArray());
	}

	private class ChannelTreeListener implements TreeSelectionListener {

		@Override
		public void valueChanged(TreeSelectionEvent e) {
			TreePath path = e.getOldLeadSelectionPath();
			DefaultTreeModel model = (DefaultTreeModel) channelTree.getModel();
			MutableTreeNode root = (MutableTreeNode) model.getRoot();
			MutableTreeNode serverroot = (MutableTreeNode) root.children().nextElement();
			TreePath newPath = e.getNewLeadSelectionPath();
			if (newPath == null) { // we parted the only channel we were in
				userList.removeAll();
				setTitle(TITLE_PREPEND);
			} else if (path != null && newPath.getLastPathComponent() != serverroot) {
				// String src = path.getLastPathComponent().toString();
				String dest = newPath.getLastPathComponent().toString();
				Channel channel = ircmanager.findChannelByName(dest);
				if (channel != null) {
					channelTextArea.setText(channel.getText());
				}
				IrcManager.currentChannel = channel;
				userList.setListData(ircmanager.getUsers(channel.getName()));
				setTitle(prepareTitle(channel.getName(), channel.getTopic()));
				channelStack.update(channel);
			}
			inputTextField.requestFocus();
		}

	}

	public void findAnnotated(String annotation, String packageName) {
		Set<String> annotatedSet = null;

		try {
			URL thisLocation = GUI.class.getProtectionDomain().getCodeSource().getLocation();
			URL packageLocation = ClasspathUrlFinder.findResourceBase(packageName.replace(".", "/"), GUI.class
				.getClassLoader());
			AnnotationDB annotationDB = new AnnotationDB();
			annotationDB.scanArchives(thisLocation, packageLocation);
			Map<String, Set<String>> annotatedClasses = annotationDB.getAnnotationIndex();
			annotatedSet = annotatedClasses.get(annotation);

			ClassLoader cl = Thread.currentThread().getContextClassLoader();
			for (String className : annotatedSet) {
				Object obj = cl.loadClass(className).newInstance();
				String name = obj.getClass().getSimpleName();
				commandMap.put(name.toLowerCase(), obj);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public javax.swing.JLabel getErrorLabel() {
		return errorLabel;
	}

	public javax.swing.JList getUserList() {
		return userList;
	}

	public javax.swing.JTextArea getChannelTextArea() {
		return channelTextArea;
	}

	public javax.swing.JTextField getInputTextField() {
		return inputTextField;
	}

	public javax.swing.JTree getChannelTree() {
		return channelTree;
	}

	public static IrcManager getIrcmanager() {
		return ircmanager;
	}

	public static String prepareMessage(String sender, String message) {
		return '\n' + sdf.format(new Date()) + " " + sender + ": " + message;
	}

	public static String prepareAction(String sender, String action) {
		return '\n' + sdf.format(new Date()) + " ***" + sender + " " + action;
	}

	public static String prepareTitle(String channel, String topic) {
		String title = TITLE_PREPEND + ": " + channel;
		if (topic != null) {
			title += " | " + topic;
		}
		return title;
	}

	@Override
	public void onMessage(Message message) {
		String text = prepareMessage(message.getSource(), message.getMessage());
		if (IrcManager.currentChannel.getName().equalsIgnoreCase(message.getSource())) {
			channelTextArea.append(text);
			IrcManager.currentChannel.appendText(text);
		} else {
			ircmanager.findChannelByName(message.getSource()).appendText(text);
		}
	}

	@Override
	public void onAction(Message message) {
		String action = prepareAction(message.getSource(), message.getMessage());
		if (IrcManager.currentChannel.getName().equalsIgnoreCase(message.getDestination())) {
			channelTextArea.append(action);
			IrcManager.currentChannel.appendText(action);
		} else {
			ircmanager.findChannelByName(message.getDestination()).appendText(action);
		}
	}

	@Override
	public void onChannelInfo(String channel, int userCount, String topic) {

	}

	@Override
	public void onUserList(String channel, User[] users) {
		if (IrcManager.currentChannel.getName().equalsIgnoreCase(channel)) {
			userList.setListData(users);
		}
	}

	@Override
	public void onTopic(String channel, String topic, String setBy, long date, boolean changed) {
		if (IrcManager.currentChannel.getName().equalsIgnoreCase(channel)) {
			setTitle(prepareTitle(channel, topic));
			if (changed) {
				channelTextArea.append("\n***" + setBy + " changed to the topic to: " + topic);
			}
		} else if (changed) {
			ircmanager.findChannelByName(channel).appendText("\n***" + setBy + " changed to the topic to: " + topic);
		}
	}

	@Override
	public void onJoin(String channel, String sender, String login, String hostname) {
		if (IrcManager.currentChannel.getName().equalsIgnoreCase(channel)) {
			channelTextArea.append("\n***" + sender + " has joined the channel");
			this.rebuildUserList(channel);
		}
	}

	@Override
	public void onPart(String channel, String sender, String login, String hostname) {
		if (IrcManager.currentChannel.getName().equalsIgnoreCase(channel)) {
			channelTextArea.append("\n***" + sender + " has left the channel");
			this.rebuildUserList(channel);
		}
	}

	@Override
	public void onQuit(String sourceNick, String sourceLogin, String sourceHostname, String reason) {
		channelTextArea.append("\n***" + sourceNick + " has quit the server");
		this.rebuildUserList(IrcManager.currentChannel.getName());
	}

}
